#include "Rutinas.h"

extern BufferCache * cache;


void captura_usr1_usr2() { //intercepta las senales USR1 y USR2
   //inicializacion para el proceso
   sigset_t masc; //mascara de senales

   //sigfillset(&masc); // Pone a 1 los bits de la mascara de señales
   //sigprocmask(SIG_BLOCK, &masc, NULL); //bloquea todas las señales

   sigemptyset(&masc); // Pone a 0 los bits de la mascara de señales
   sigaddset(&masc, SIGUSR1);
   sigaddset(&masc, SIGUSR2);
   sigaddset(&masc, SIGINT); //esto es un anadido. para poder hacer Ctrl + C
   sigprocmask(SIG_UNBLOCK, &masc, NULL); //desbloquea senales SIGUSR1 y SIGUSR2

   //captura de la señal SIGUSR1
   struct sigaction act;
   act.sa_flags = SA_SIGINFO; //tres argumentos -> configurar sa_sigaction
   act.sa_sigaction = my_sigusr1; //(void *)

   if (sigaction(SIGUSR1, &act, (struct sigaction *) NULL)) { //intercepta senal, arg3: null: accion anterior no se guarda
      printf("Sigaction dio error = %d", errno);
      exit(0);
   }

   //captura de la señal SIGUSR2
   struct sigaction act2;
   act2.sa_flags = SA_SIGINFO; //tres argumentos -> configurar sa_sigaction
   act2.sa_sigaction = my_sigusr2;

   if (sigaction(SIGUSR2, &act2, (struct sigaction *) NULL)) { //intercepta senal, arg3: null: accion anterior no se guarda
      printf("Sigaction dio error = %d", errno);
      exit(0);
   }
}


//funcion de interceptacion de la senal SIGUSR1: 
//para despertar a los procesos dormidos (esperando que se desbloquee un buffer o esperando que se libere cualquier buffer)
void my_sigusr1(int n, siginfo_t *info, void *p) { 
   //funcion vacia, no hace falta hacer nada.
   chivato("pid=%d. Me llego SIGUSR1. He despertado de esperar a liberar buffer\n", getpid());
}


//funcion de interceptacion de la senal SIGUSR2:
//para la implementacion de escritura retardada 
void my_sigusr2(int n, siginfo_t *info, void *p) { //el proceso que abrio el fichero del primer buffer de freelist, escribe en disco
   //no hace falta uso de semaforo
   int nbuf = cache->freeStart; //coger el primero de la freelist
   lseek(cache->bufs[nbuf].fd, cache->bufs[nbuf].idbloque.bloque * TAM_BLOQUE, 0);

   chivato("pid=%d. Me llego SIGUSR2. Tengo que escribir a disco desde buffer %d, bloque=%d, nbytesvalidos=%d, lseek=%d \n", \
 getpid(), nbuf, cache->bufs[nbuf].idbloque.bloque, cache->bufs[nbuf].nbytesValidos, cache->bufs[nbuf].idbloque.bloque * TAM_BLOQUE);

   /*for (int i = 0; i < cache->bufs[nbuf].nbytesValidos; i++)
      printf("%c", cache->bufs[nbuf].datos[i]);*/
   
   write(cache->bufs[nbuf].fd, cache->bufs[nbuf].datos, cache->bufs[nbuf].nbytesValidos); //disco <- buf.datos
   cache->bufs[nbuf].delayWrite = 0;
   //cache->bufs[nbuf].nbytesValidos = nwriten; //bueno, si se hizo antes bread, no haria falta, xq seria igual
   cache->bufs[nbuf].datosValidos = 1;
}


//*********************************************************************
void sleepUntilFree() {
  chivato("--- pid = %d. getblk: sleepUntilFree\n ", getpid());
  sigset_t masc; //mascara de señales bloqueadas(1)/no bloqueadas(0)

  sigfillset(&masc); // Pone a 1 los bits de la mascara de señales (las bloquea todas)
  sigdelset(&masc, SIGUSR1); // Pone a 0 el bit correspondiente a la señal SIGUSR1 (la desbloquea)
  sigdelset(&masc, SIGUSR2); //necesario xq si alguien le dice de hacer escritura retardada. despues podria volverse a dormir sin problemas
  sigdelset(&masc, SIGINT); //esto es un anadido. para poder hacer Ctrl + C
  int sem = semAt(SEM_ID_CACHE); //coge semaforo (mem compartida)
  semSignal(sem); //liberamos semaforo
  //aqui se puede desplanificar. solucion: mandar senal USR1 manualmente
  sigsuspend(&masc); //suspende el proceso, solo despertara cuando se le envie una senal desbloquea: SIGUSR1
  //sigsuspend establece mascara, duerme, y al retornar pone la mascara como estaba antes
}


//Asignacion de buffer
int getblk(int fs, int inodo, int bloque) {  //devuelve buffer bloqueado (que puede o no contener los datos que estamos buscando).

   int sem = semAt(SEM_ID_CACHE); //coge semaforo (mem compartida)
   
   while (1) { //while (!bufferFound) //mientras no se encuentre buffer
      chivato("--- pid = %d. getblk: entro en wait\n ", getpid());
      semWait(sem);
      chivato("--- exmut pid = %d. getblk: sale del wait y hace findHash\n ", getpid());
      int nbuf = bc_findHash(fs, inodo, bloque); //si esta tambien en freelist sigue dando true ehhh
      if (nbuf != NIL) { //si buffer esta en hash
         if ((cache->bufs[nbuf].bloqueado) && (cache->bufs[nbuf].pid != getpid())) { //bloqueado por otro que no es el
            chivato("--- exmut pid = %d. getblk: Escenario 5: buf=%d esta en hash y esta bloqueado: duermo por este\n ", getpid(), nbuf);
            if (cache->bufs[nbuf].nslept >= MAX_SLEPT) {
               printf("exmut Lo sentimos, se ha superado el numero maximo de procesos durmiendo por el buffer %d\n", nbuf);
               bc_print();
               semSignal(sem);
               exit(0);
            }

            cache->bufs[nbuf].slept[cache->bufs[nbuf].nslept] = getpid(); //espera a que se desbloquee este buffer
            cache->bufs[nbuf].nslept++;
            sleepUntilFree(); //implicito semSignal(sem); //liberamos semaforo
            chivato("--- pid = %d. Escenario 5: despierto por buffer %d y fin.\n ", getpid(), nbuf);
            continue; //al volver al while hace semWait(sem)
         } //if bloqueado
         
         //else
         chivato("--- exmut pid = %d. getblk: Escenario 1: buf=%d esta en hash y no bloqueado: quita de freelist\n", getpid(), nbuf);
         cache->bufs[nbuf].bloqueado = 1; //lo bloquea
         bc_extractFree(nbuf); //se quita de free_list para poder ser usado
         //check_corruption("exmut despues de bc_extractFree en scenario 1");
         chivato("--- exmut pid = %d. Escenario 1: fin.\n ", getpid());
         semSignal(sem);
         return nbuf;
      }
      else { //buffer no esta en la hash
         if (bc_freeListVacia()) {
            chivato("--- exmut pid = %d. getblk: Escenario 4: no esta en hash y free list vacia: duermo por cualquiera\n ", getpid());
            if (cache->nslept >= MAX_SLEPT) {
               printf("exmut Lo sentimos, se ha superado el numero maximo de procesos durmiendo por cualquier buffer");
               bc_print();
               semSignal(sem);
               exit(0);
            }

            cache->bc_slept[cache->nslept] = getpid(); //espera a que cualquier buffer se libere
            cache->nslept++;
            sleepUntilFree(); //semSignal(sem) implicito; //liberamos semaforo
            chivato("--- pid = %d. Escenario 4: despierto por cualquier buffer y fin.\n ", getpid());
            continue; //al volver al while hace semWait(sem)
         }

         nbuf = cache->freeStart; //coger el primero de la freelist //bc_findFree(fs, bloque);

         if (cache->bufs[nbuf].delayWrite == 1) { //se habia llamado a bwrite q solo pone DW = 1
            chivato("--- exmut pid = %d. Escenario 3: no esta en hash y primero de freelist buf=%d esta DW: kill to %d\n ", getpid(), nbuf, cache->bufs[nbuf].pid);
            if (kill(cache->bufs[nbuf].pid, SIGUSR2) < 0) //manda a proceso que abrio buffer para que escriba: ponga DW = 0
               perror("error kill en usr2: "); //solo imprime mensaje, aun sigue despues
   
            while (cache->bufs[nbuf].delayWrite == 1); //espera activa: a que el otro proceso ponga DW = 0
            chivato("--- exmut pid = %d. Escenario 3: fin.\n ", getpid());
            semSignal(sem);
            continue; //al volver al while hace semWait(sem)
         } //if DW

         chivato("--- exmut pid = %d. Escenario 2: no en hash y primero de freelist buf=%d no esta DW: inserta nuevo\n ", getpid(), nbuf);
         bc_extractHash(nbuf); //elimina de anterior lista hash. NO llama a insertFree
         //check_corruption("exmut despues de bc_extractHash en scenario 2");
         //bc_extractFree(nbuf); //esta implicitamente en insertHash()
         nbuf = bc_insertHash(fs, inodo, bloque); //inserta en nueva lista hash (al principio)
         //check_corruption("exmut despues de bc_insertHash en scenario 2");
         cache->bufs[nbuf].bloqueado = 1; //lo bloquea
         //buf.datosValidos = 0, nbytesValidos = 0, fs, inodo, bloque //ya lo hace insertHash()
         //cache->bufs[nbuf].delayWrite = 0; //no hace falta, ya esta a 0
         chivato("--- exmut pid = %d. Escenario 2: fin.\n ", getpid());
         semSignal(sem); //liberamos semaforo
         return nbuf;
      }

   } //while

}


void brelse(int nbuf) { //(Buffer & buf) { //liberacion del buffer. entrada: buffer bloqueado.
   chivato("- pid = %d. inicio brelse: wait\n", getpid());
   int sem = semAt(SEM_ID_CACHE); //coge semaforo (mem compartida)
   semWait(sem);

   bc_insertFreeEnd(nbuf);
   //check_corruption("exmut despues de bc_insertFreeEnd en brelse");
   cache->bufs[nbuf].bloqueado = 0; //desbloquea buffer

   for (int i = 0; i < MAX_SLEPT; i++) { //despierta todos los procesos que esperan por este buffer o por cualquiera
      int pid;
      pid = cache->bc_slept[i];
      if (pid > 0) {
         chivato("--- exmut pid = %d. brelse: despierto a %d para cualquier buffer\n ", getpid(), pid);
         if (kill(pid, SIGUSR1) < 0) //despierta a proceso que esperaba a que se libere CUALQUIER buffer
            perror("error kill en usr1: ");
      }
      cache->bc_slept[i] = NIL; //quita al proceso como dormido

      pid = cache->bufs[nbuf].slept[i];
      if (pid > 0) {
         chivato("--- exmut pid = %d. brelse: despierto a %d para el buffer %d\n ", getpid(), pid, nbuf);
         if (kill(pid, SIGUSR1) < 0) //despierta a proceso que esperaba a que se libere ESTE buffer
            perror("error kill en usr1: ");
      }
      cache->bufs[nbuf].slept[i] = NIL; //quita al proceso como dormido
      
   } //for. 

   cache->nslept = 0; //numero de procesos durmiendo = 0
   cache->bufs[nbuf].nslept = 0;
   chivato("- exmut pid = %d. fin brelse\n", getpid());
   semSignal(sem); //es gracioso xq los despierto pero aun seguiran esperando con semWait() hasta llegar aqui
}


int bread(int fd, int fs, int inodo, int bloque) { //Lectura de buffer de disco. devuelve buffer bloqueado. amañada: uso de fd para llamar read
   chivato("- pid = %d. inicio bread: wait\n", getpid());
   int nbuf = getblk(fs, inodo, bloque);

   int sem = semAt(SEM_ID_CACHE); //coge semaforo (mem compartida)
   semWait(sem); //tiene que ser despues de getblk para que no hayan dos wait() seguidos

   if ((cache->bufs[nbuf].datosValidos) && (cache->bufs[nbuf].fd == fd)) {
      semSignal(sem);
      return nbuf;
   }

   //else: inicializa lectura disco
   cache->bufs[nbuf].fd = fd;
   chivato("- exmut pid = %d. bread: lseek(%d)\n", getpid(), bloque * TAM_BLOQUE);
   lseek(fd, bloque * TAM_BLOQUE, 0); //cache->nbufs[nbuf].fd...


   int nbytes_validos = read(fd, cache->bufs[nbuf].datos, TAM_BLOQUE); //pone lo leido en buffer.datos. se contempla caso EOF: nbytesVal = 0
   chivato("- exmut pid = %d. bread: bufs[%d].nbytes_validos=%d\n", getpid(), nbuf, nbytes_validos);
   //if (nbytes_validos == -1) perror("fallo read\n");

   cache->bufs[nbuf].nbytesValidos = nbytes_validos; //con read ya se hace implicita la espera a q termine la lectura de disco
   
   if (nbytes_validos == 0)
      cache->bufs[nbuf].datosValidos = 0;
   else
      cache->bufs[nbuf].datosValidos = 1;
   
   cache->bufs[nbuf].delayWrite = 0;

   chivato("- exmut pid = %d. fin bread\n", getpid());
   semSignal(sem);
   return nbuf;
      
}


void bwrite(int nbuf) { //escritura de bloque de disco
   int sem = semAt(SEM_ID_CACHE); //coge semaforo (mem compartida)
   semWait(sem);
   chivato("- pid = %d. inicio bwrite: despues de wait", getpid());

   cache->bufs[nbuf].pid = getpid();
   cache->bufs[nbuf].delayWrite = 1; 
   cache->bufs[nbuf].datosValidos = 1;

   chivato("- exmut pid = %d. fin bwrite\n", getpid());
   semSignal(sem);
}


